#include <sstream>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include "Config.h"
#include "Emboard.h"
#include "SkyeyeImpl.h"

using namespace etb;

static const char* MACHINE_STATES[] = {
  ETB_CMD_PARAM_RUN, 
  ETB_CMD_PARAM_STOP,
  ETB_CMD_PARAM_STEP
};

Emboard::Emboard(EventBus* bus)
  : bus_(bus), state_(MACHINE_STOP)
{
  SkyeyeImpl::getInst()->setCallback(this);
}

Emboard::~Emboard()
{
}

void Emboard::init(bool bootMode)
{
  if (bus_)
    bus_->registerEventClient(this);

  checkToCreateNorFlash();
  checkToCreateNandFlash();

  const char *config = CONF_CONTENT_NAND;
  if (bootMode)  config = CONF_CONTENT_NOR;

  SkyeyeImpl::getInst()->initMach(config);
}

void Emboard::handleEvent(const std::string event)
{
  if (event.empty()) return;

  EventStruct es;
  parseEvent(event, &es);

  if (es.target.compare(ETB_CMD_TARGET_EM) == 0) {
    L_DEBUG("[EMBOARD] event received - " << event.c_str());

    if (es.device.compare(ETB_CMD_DEV_PWR_SWI) == 0) {
      if (es.param.compare(ETB_CMD_PARAM_ON) == 0) {
        runSim();
        std::string evt = makeCmd(ETB_CMD_TARGET_VI, 
                                    ETB_CMD_DEV_PWR_LED, 
                                    ETB_CMD_PARAM_ON);
        bus_->sendEvent(evt);
      } else if (es.param.compare(ETB_CMD_PARAM_OFF) == 0) {
        stopSim();
        std::string evt = makeCmd(ETB_CMD_TARGET_VI, 
                                    ETB_CMD_DEV_PWR_LED, 
                                    ETB_CMD_PARAM_OFF);
        bus_->sendEvent(evt);
      }
    } else if (es.device.compare(ETB_CMD_DEV_KEY1) == 0) {
      if (es.param.compare(ETB_CMD_PARAM_PRESS) == 0) {
        keyPressed(1);
      } else if (es.param.compare(ETB_CMD_PARAM_RELEASE) == 0) {
        keyReleased(1);
      }
    } else if (es.device.compare(ETB_CMD_DEV_KEY2) == 0) {
      if (es.param.compare(ETB_CMD_PARAM_PRESS) == 0) {
        keyPressed(2);
      } else if (es.param.compare(ETB_CMD_PARAM_RELEASE) == 0) {
        keyReleased(2);
      }
    } else if (es.device.compare(ETB_CMD_DEV_KEY3) == 0) {
      if (es.param.compare(ETB_CMD_PARAM_PRESS) == 0) {
        keyPressed(3);
      } else if (es.param.compare(ETB_CMD_PARAM_RELEASE) == 0) {
        keyReleased(3);
      }
    } else if (es.device.compare(ETB_CMD_DEV_KEY4) == 0) {
      if (es.param.compare(ETB_CMD_PARAM_PRESS) == 0) {
        keyPressed(4);
      } else if (es.param.compare(ETB_CMD_PARAM_RELEASE) == 0) {
        keyReleased(4);
      }
    } else if (es.device.compare(ETB_CMD_DEV_KEY5) == 0) {
      if (es.param.compare(ETB_CMD_PARAM_PRESS) == 0) {
        keyPressed(5);
      } else if (es.param.compare(ETB_CMD_PARAM_RELEASE) == 0) {
        keyReleased(5);
      }
    } else if (es.device.compare(ETB_CMD_DEV_KEY6) == 0) {
      if (es.param.compare(ETB_CMD_PARAM_PRESS) == 0) {
        keyPressed(6);
      } else if (es.param.compare(ETB_CMD_PARAM_RELEASE) == 0) {
        keyReleased(6);
      }
    } else if (es.device.compare(ETB_CMD_DEV_RESET_KEY) == 0) {
      if ((es.param.compare(ETB_CMD_PARAM_RELEASE) == 0) && 
          (state_ == MACHINE_RUN) ) {
        jtagResetMachine();
      }
    }
  }
}

void Emboard::handleCallback(const std::string type, const std::string event)
{
  if (type.compare(0, 3, ETB_CMD_DEV_LCD) == 0) {
    std::string evt = makeCmd(ETB_CMD_TARGET_VI, 
                                ETB_CMD_DEV_LCD, 
                                event);
    bus_->sendEvent(evt);
  } else if (type.compare(0, 4, ETB_CMD_DEV_LED1) == 0) {
    std::string evt = makeCmd(ETB_CMD_TARGET_VI, 
                                ETB_CMD_DEV_LED1, 
                                event);
    bus_->sendEvent(evt);
  } else if (type.compare(0, 4, ETB_CMD_DEV_LED2) == 0) {
    std::string evt = makeCmd(ETB_CMD_TARGET_VI, 
                                ETB_CMD_DEV_LED2, 
                                event);
    bus_->sendEvent(evt);
  } else if (type.compare(0, 4, ETB_CMD_DEV_LED3) == 0) {
    std::string evt = makeCmd(ETB_CMD_TARGET_VI, 
                                ETB_CMD_DEV_LED3, 
                                event);
    bus_->sendEvent(evt);
  } else if (type.compare(0, 4, ETB_CMD_DEV_LED4) == 0) {
    std::string evt = makeCmd(ETB_CMD_TARGET_VI, 
                                ETB_CMD_DEV_LED4, 
                                event);
    bus_->sendEvent(evt);
  }
}

void Emboard::runSim()
{
  if (state_ == MACHINE_RUN) 
    return;

  SkyeyeImpl::getInst()->runMach();
  setMachineState(MACHINE_RUN);
}

void Emboard::stopSim()
{
  if (state_ == MACHINE_STOP) return;

  SkyeyeImpl::getInst()->stopMach();
  setMachineState(MACHINE_STOP);
}

void Emboard::pauseSim()
{
  if (state_ == MACHINE_STOP) return;

  SkyeyeImpl::getInst()->pauseMach();
  setMachineState(MACHINE_STEP);
}

void Emboard::stepSim()
{
  if (state_ != MACHINE_STOP && state_ != MACHINE_STEP) 
    return;

  SkyeyeImpl::getInst()->stepRun();
  setMachineState(MACHINE_STEP);
}

void Emboard::keyPressed(int keyNum)
{
  if (state_ != MACHINE_RUN) return;
  SkyeyeImpl::getInst()->keyPressed(keyNum);
}

void Emboard::keyReleased(int keyNum)
{
  if (state_ != MACHINE_RUN) return;
  SkyeyeImpl::getInst()->keyReleased(keyNum);
}

void Emboard::jtagGetCpuState(std::vector<U32>& regs)
{
  SkyeyeImpl::getInst()->dumpRegs(regs);
}

bool Emboard::jtagStepRunMachine()
{
  if (state_ != MACHINE_RUN && state_ != MACHINE_STEP) return false;

  if (state_ == MACHINE_RUN) {
    stopSim();
  }
  stepSim();

  return true;
}

void Emboard::jtagPauseMachine()
{
  if (state_ != MACHINE_RUN) return;
  pauseSim();
}

void Emboard::jtagContinueMachine()
{
  SkyeyeImpl::getInst()->continueMach();
  setMachineState(MACHINE_RUN);
}

U32 Emboard::jtagGetInstruction()
{
  if (state_ != MACHINE_STEP) return 0;

  return SkyeyeImpl::getInst()->getInstruction();
}

U32 Emboard::jtagDownloadBinary(U8 target, U8 *binPtr, U32 addrOffset, U32 size)
{
  U32 n = 0;
  if (!binPtr) return 0;

  if (target == DOWNLOAD_TO_NOR) {
    if (addrOffset + size > NORFLASH_SIZE) return 0;
#if defined(__MINGW__) || defined(__MINGW32__)
    int f = open(NORFLASH_FILENAME, O_RDWR | O_BINARY);
#else
    int f = open(NORFLASH_FILENAME, O_RDWR);
#endif
    if (f < 0) return 0;
    off_t p = lseek(f, addrOffset, SEEK_SET);
    
    if (p >= 0) {
        n = write(f, binPtr, size);
    }
    close(f);

    return n;
  } else if (target == DOWNLOAD_TO_SDRAM) {
    if (addrOffset + size > MEMORY_BASEADDR + MEMORY_SIZE) 
        return 0;

    return SkyeyeImpl::getInst()->writeToMemory(binPtr, 
            MEMORY_BASEADDR + addrOffset, size);
  } else if (target == DOWNLOAD_TO_NAND) {
    if (addrOffset % 512 || (addrOffset + size > NANDFLASH_SIZE)) {
      return -1;
    }
    // TODO: finish download to nand flash
  }

  return 0;
}

void Emboard::jtagResetMachine()
{
  stopSim();
  runSim();
  std::string evt = makeCmd(ETB_CMD_TARGET_VI, 
                              ETB_CMD_DEV_PWR_LED, 
                              ETB_CMD_PARAM_ON);
  bus_->sendEvent(evt);
}

void Emboard::jtagSetPc(U32 addr)
{
  SkyeyeImpl::getInst()->setPc(addr);
}

void Emboard::checkToCreateNorFlash()
{
  // Is nor flash file corrupted?
  U8 flag = 0;

  struct stat buf;
  stat(NORFLASH_FILENAME, &buf);
  if (buf.st_size != NORFLASH_SIZE)
    flag = 1;

  if (access(NORFLASH_FILENAME, R_OK | W_OK) || flag) {

    // Create NOR FLASH file
    FILE *f = fopen(NORFLASH_FILENAME, "w");
    if (f) {
      U8 *c = (U8*)malloc(NORFLASH_SIZE);
      memset(c, 0x0, NORFLASH_SIZE);
      fwrite(c, 1, NORFLASH_SIZE, f);
      free(c);
    }
    fclose(f);
  }
}

void Emboard::checkToCreateNandFlash()
{
  // Is nand flash file corrupted?
  U8 flag = 0;

  struct stat buf;
  stat(NANDFLASH_FILENAME, &buf);
  if (buf.st_size != NANDFLASH_SIZE)
    flag = 1;

  if (access(NANDFLASH_FILENAME, R_OK | W_OK) || flag) {
    // Create NAND FLASH file
    FILE *f = fopen(NANDFLASH_FILENAME, "w");
    if (f) {
      U8 *c = (U8*)malloc(NANDFLASH_SIZE);
      memset(c, 0xFF, NANDFLASH_SIZE);
      fwrite(c, 1, NANDFLASH_SIZE, f);
      free(c);
    }
    fclose(f);
  }
}

void Emboard::setMachineState(U8 state)
{
  L_INFO("[EMBOARD] Set machine state from " 
         << MACHINE_STATES[state_]
         << " to " << MACHINE_STATES[state]);

  state_ = state;
  std::string evt = makeCmd(ETB_CMD_TARGET_VI, 
                              ETB_CMD_DEV_STATE, 
                              MACHINE_STATES[state]);
  bus_->sendEvent(evt);
}
